use std::fs::{self, File};
use std::io::{copy, Seek};
use std::path::Path;
use std::{io::{Read, Write}};
use gmsm::sm2::*;
use walkdir::{WalkDir, DirEntry};
use zip::result::ZipError;
use zip::write::FileOptions;

#[derive(serde::Serialize)]
pub struct CustomResponse {
  pub value: String,
}

pub struct  Encrypt {}

impl Encrypt {

    pub fn doit(
        src_dir: &str,
        dst_file: &str,
        method: zip::CompressionMethod,
    ) -> zip::result::ZipResult<()> {
        if !Path::new(src_dir).is_dir() {
            return Err(ZipError::FileNotFound);
        }

        let path = Path::new(dst_file);
        let file = File::create(path).unwrap();

        let walkdir = WalkDir::new(src_dir);
        let it = walkdir.into_iter();

        Encrypt::zip_dir(&mut it.filter_map(|e| e.ok()), src_dir, file, method)?;

        Ok(())
    }

    pub fn zip_dir<T>(
        it: &mut dyn Iterator<Item = DirEntry>,
        prefix: &str,
        writer: T,
        method: zip::CompressionMethod,
    ) -> zip::result::ZipResult<()>
    where
        T: Write + Seek,
    {
        let mut zip = zip::ZipWriter::new(writer);
        let options = FileOptions::default()
            .compression_method(method)
            .unix_permissions(0o755);

        let mut buffer = Vec::new();
        for entry in it {
            let path = entry.path();
            let name = path.strip_prefix(Path::new(prefix)).unwrap();

            // Write file or directory explicitly
            // Some unzip tools unzip files with directory paths correctly, some do not!
            if path.is_file() {
                println!("adding file {:?} as {:?} ...", path, name);
                #[allow(deprecated)]
                zip.start_file_from_path(name, options)?;
                let mut f = File::open(path)?;

                f.read_to_end(&mut buffer)?;
                zip.write_all(&*buffer)?;
                buffer.clear();
            } else if !name.as_os_str().is_empty() {
                // Only if not root! Avoids path spec / warning
                // and mapname conversion failed error on unzip
                println!("adding dir {:?} as {:?} ...", path, name);
                #[allow(deprecated)]
                zip.add_directory_from_path(name, options)?;
            }
        }
        zip.finish()?;

        Result::Ok(())
    }

    pub fn zip(
        src_dir: String,
        dst_file: String
    ) -> Result<CustomResponse, String> {
        println!("srcDir {} dstFile {}", src_dir, dst_file);
        match Encrypt::doit(&src_dir, &dst_file, zip::CompressionMethod::Stored) {
            Ok(_) => println!("done: {} written to {}", src_dir, dst_file),
            Err(e) => println!("Error: {:?}", e),
        }

        Ok(CustomResponse {
            value: dst_file
        })
    }

    pub fn unzip(
        _dst_file: String,
        _target: String
    ) -> Result<CustomResponse, ZipError> {
        let dst_file = Path::new(&_dst_file);
        let target = Path::new(&_target);
        let zipfile = std::fs::File::open(&dst_file).unwrap();
        let mut zip = zip::ZipArchive::new(zipfile)?;

        println!("target {}", _target);
        println!("source {}", _dst_file);

        for i in 0..zip.len() {
            let mut file = zip.by_index(i).unwrap();
            if file.is_dir() {
                println!("file utf8 path {:?}", file.name_raw());
                let target = target.join(Path::new(&file.name().replace("\\", "")));
                println!("create {:?}", target);
                fs::create_dir_all(target).unwrap();
            } else {
                let file_path = target.join(Path::new(file.name()));
                if let Some(p) = file_path.parent() {
                    if !p.exists() {
                        fs::create_dir_all(p).unwrap();
                    }
                }
                let mut target_file = if !file_path.exists() {
                    println!("file path {}", file_path.to_str().unwrap());
                    fs::File::create(file_path).unwrap()
                } else {
                    fs::File::open(file_path).unwrap()
                };

                match copy(&mut file, &mut target_file) {
                    Ok(_) => println!("done: {} extract to {}", _dst_file, _target),
                    Err(e) => println!("Error: {:?}", e),
                }
            }
        }

        Ok(CustomResponse {
            value: dst_file.to_str().unwrap().to_string()
        })
    }

    pub fn encrypt_content(content: &str) -> Result<CustomResponse, String>  {
        let pk = "04bc176ac05df0212410b0e727d2a2c11e0b82287dc90d213342ee35c52af223d9ec2e9f1e2860107210f9618299d7150592838e8af9b88a81a181840d721ee8f4";
        let encrypt = sm2_encrypt_c1c3c2(&content, pk);

        Ok(CustomResponse {
            value: encrypt
        })
    }

    pub fn decrypt_content(content: &str) -> Result<CustomResponse, String> {
        let sk = "f359b9fbdf82b98629aff8ed4956ad216cbd388214445d87f391a5998cc21e74";
        let decrypt = sm2_decrypt_c1c3c2(&content, sk);

        Ok(CustomResponse {
            value: decrypt
        })
    }

    pub fn mix_file(
        source_file: &str,
        target_file: &str,
        encrypt: bool
    ) {
        let len = fs::metadata(source_file).unwrap().len();
        println!("{}", len);
        let mut source = File::open(source_file).unwrap();
        let mut target = File::create(target_file).unwrap();

        let length = if len <= 1024 { 64 } else if len > 1024 && len < 10240 { 1024 } else { 10240 };

        let mut buffer = vec![0u8; length];
        const ENCRYPTOR: &[u8] = "encryptor".as_bytes();
        const ENLEN: usize = ENCRYPTOR.len();
        let mut pass_buffer = [0u8; ENLEN];

        loop {
            let nbytes = source.read(&mut buffer).unwrap();
            target.write(&buffer[..nbytes]).unwrap();
            // println!("{}", buffer.len());

            if encrypt {
                // 写入混淆
                target.write(ENCRYPTOR).unwrap();
            } else {
                // 忽略混淆
                source.read(&mut pass_buffer).unwrap();
            }

            if nbytes < buffer.len() { break; }
        }
    }
}
